

#include "gameplayscene.h"
#include "animation.h"
#include "gameuilayer.h"
#include "resourcemanager.h"
#include "levelmanager.h"
#include "messagequeue.h"
#include "gamemanager.h"
#include "gamedatamanager.h"
#include "CCSkeletonCache.h"
#include "bullet.h"
#include "tower.h"
#include "rallypoint.h"
#include "camp.h"
#include "enemy.h"
#include "rallypoint.h"
#include "mine.h"
#include "towerfactory.h"
#include "unitfactory.h"
#include "flagmenulayer.h"
#include "soldier.h"
#include "rangecircle.h"

static const float TOUCH_MOVEMENT_THRESHOLD = 10.0f;

GamePlayScene::GamePlayScene(): 
	_bgLayer(nullptr)
	, _uiLayer(nullptr)
	, _gameLayer(nullptr)
	, _selectedFlag(nullptr)
	, _selectedHero(nullptr)
	, _selectedSoldier(nullptr)
	, camp(nullptr)
	, gameState(GamePlayState::standby)
	, _touchedPlace(GamePlaySceneTouchPlace::blank)
{
	holderName = "gameplay_scene";
}

GamePlayScene::~GamePlayScene() {

}

bool GamePlayScene::init() {
	if (!Scene::init()) {
		return false;
	}

	LM->loadLevel("levels/level_1.xml");
	LM->gamePlayScene = this;

	_bgLayer = Layer::create();
	addChild(_bgLayer);

	sprintf(buf, "textures/scenes/%s", LM->sceneBackgroundName.c_str());
	RM->createBackground(_bgLayer, buf);

	_gameLayer = Layer::create();
	addChild(_gameLayer);

	_uiLayer = GameUILayer::create();
	addChild(_uiLayer);

	_flagMenuLayer = FlagMenuLayer::create();
	_flagMenuLayer->setVisible(false);
	addChild(_flagMenuLayer);


	addSceneEntities();
	addTowers();
	addMines();

	_uiLayer->showEnemyWaveIndicator();

	GM->setGamePlayScene(this);

	scheduleUpdate();

	return true;
}

void GamePlayScene::update(float ticks) {
	ticks = 0.034f;
	handleMessage();

	switch(gameState) {
	case GamePlayState::standby:
		gameState = GamePlayState::play;
		break;
	case GamePlayState::play: {
		LM->update(ticks);

		updateBullets(ticks);
		updateEnemies(ticks);
		updateSoldiers(ticks);
		updateHeroes(ticks);
		updateFrameAnimations(ticks);
		updateSoldierCreationList(ticks);
		reorderNodes();

		break;
							  }
	default:break;
	}

	if(gameState == GamePlayState::standby || gameState == GamePlayState::play) {
		if(_flagMenuLayer->isVisible()) {
			_flagMenuLayer->update(ticks);
		}
	}

}


void GamePlayScene::handleMessage() {
	auto msg = PEEK_MSG(holderName);
	if(msg) {
		switch(msg->key){
		case MessageType::count:
			break;
		default:break;
		}

		POP_MSG(holderName);
	}
}

void GamePlayScene::updateBullets(float ticks) {
	for(auto it = bullets.begin(); it != bullets.end();) {
		Bullet* bullet = *it;
		if(!bullet->update(ticks)) {
			it = bullets.erase(it);
			delete bullet;
		} else {
			++it;
		}
	}

}

bool GamePlayScene::ccTouchBegan( Touch *pTouch, Event *pEvent )
{
	bool ret = _uiLayer->ccTouchBegan(pTouch, pEvent);
	if(ret) {
		return true;
	}

	if(_flagMenuLayer->isVisible()) {
		ret = _flagMenuLayer->ccTouchBegan(pTouch, pEvent);
		if(ret) {
			return true;
		}
	}

	Point pt = pTouch->getLocation();
	Point logicPos = GM->getLogicPosition(pt);

	if(_selectedHero) {
		_selectedHero->rangeCircle->setVisible(false);
		_selectedHero = nullptr;
	}
	if(_selectedSoldier) {
		setSoldierRangeCircleVisibility(_selectedSoldier->myFlag, false);
		_selectedSoldier = nullptr;
	}

	_selectedFlag = getTouchedFlag(logicPos);
	_selectedHero = getTouchedHero(logicPos);
	_selectedSoldier = getTouchedSoldier(logicPos);

	float minDistance = 9999.0f;
	if(_selectedFlag) {
		float distance = logicPos.getDistance(_selectedFlag->position);
		minDistance = distance;
		_touchedPlace = GamePlaySceneTouchPlace::flag;
	}

	if(_selectedHero) {
		float distance = logicPos.getDistance(_selectedHero->position);
		if(distance < minDistance) {
			minDistance = distance;
			_touchedPlace = GamePlaySceneTouchPlace::hero;
		}
	}

	if(_selectedSoldier) {
		float distance = logicPos.getDistance(_selectedSoldier->position);
		if(distance < minDistance) {
			minDistance = distance;
			_touchedPlace = GamePlaySceneTouchPlace::soldier;
		}
	}

	switch (_touchedPlace)
	{
	case GamePlaySceneTouchPlace::blank:
		break;
	case GamePlaySceneTouchPlace::flag:
		if(_flagMenuLayer->isVisible()) {
			_flagMenuLayer->showLayer(SCALE_PT(_selectedFlag->position));
		}
		break;
	case GamePlaySceneTouchPlace::camp:
		break;
	case GamePlaySceneTouchPlace::dragging_flag:
		break;
	case GamePlaySceneTouchPlace::soldier:
		setSoldierRangeCircleVisibility(_selectedSoldier->myFlag, true);
		_selectedHero = nullptr;
		break;
	case GamePlaySceneTouchPlace::hero:
		_selectedHero->rangeCircle->setVisible(true);
		_selectedSoldier = nullptr;
		break;
	case GamePlaySceneTouchPlace::dragging_hero:
		break;
	default:
		break;
	}

	if(!_selectedFlag) {
		if(_flagMenuLayer->isVisible()) {
			_flagMenuLayer->closeLayer();
		}
	}

	return true;
}

void GamePlayScene::ccTouchMoved( Touch *pTouch, Event *pEvent )
{
	Point pt = pTouch->getLocation();
	Point start = GM->getLogicPosition(pTouch->getStartLocation());
	Point logicPos = GM->getLogicPosition(pt);

	switch(_touchedPlace) {
	case GamePlaySceneTouchPlace::blank:
		break;
	case GamePlaySceneTouchPlace::camp:
		break;
	case GamePlaySceneTouchPlace::flag:{
		if(fabs(start.x - logicPos.x) > TOUCH_MOVEMENT_THRESHOLD || fabs(start.y - logicPos.y) > TOUCH_MOVEMENT_THRESHOLD) {
			_touchedPlace = GamePlaySceneTouchPlace::dragging_flag;
		}
		break;
									   }
	case GamePlaySceneTouchPlace::hero: {
		if(fabs(start.x - logicPos.x) > TOUCH_MOVEMENT_THRESHOLD || fabs(start.y - logicPos.y) > TOUCH_MOVEMENT_THRESHOLD) {
			_touchedPlace = GamePlaySceneTouchPlace::dragging_hero;
		}
		break;
										}
	case GamePlaySceneTouchPlace::dragging_flag:{
		_selectedFlag->position = logicPos;
		_selectedFlag->getSprite()->setPosition(pt);

		break;
												}
	default:break;
	}
}

void GamePlayScene::ccTouchEnded( Touch *pTouch, Event *pEvent )
{
	Point pt = pTouch->getLocation();
	Point logicPos = GM->getLogicPosition(pt);

	if(_flagMenuLayer->isVisible()) {
		_flagMenuLayer->ccTouchEnded(pTouch, pEvent);
		if(_flagMenuLayer->soldierType != SoldierType::count) {
			_soldierCreationList.push_back(SoldierCreationInfo(_flagMenuLayer->soldierType, _selectedFlag, 0.0f));
			_soldierCreationList.push_back(SoldierCreationInfo(_flagMenuLayer->soldierType, _selectedFlag, 0.5f));
			_soldierCreationList.push_back(SoldierCreationInfo(_flagMenuLayer->soldierType, _selectedFlag, 1.0f));
		}

		_flagMenuLayer->closeLayer();
		_selectedFlag = nullptr;
	} else {
		switch(_touchedPlace) {
		case GamePlaySceneTouchPlace::blank:

			break;
		case GamePlaySceneTouchPlace::camp:
			break;
		case GamePlaySceneTouchPlace::flag:{
			_flagMenuLayer->showLayer(SCALE_PT(_selectedFlag->position));
			break;
										   }
		case GamePlaySceneTouchPlace::dragging_flag:
			moveSoldiers(_selectedFlag);
			break;
		case GamePlaySceneTouchPlace::dragging_hero:
			moveHero(_selectedHero, logicPos);
			break;
		case GamePlaySceneTouchPlace::hero:
			break;
		case GamePlaySceneTouchPlace::soldier:
			break;
		default:break;
		}
	}

	_touchedPlace = GamePlaySceneTouchPlace::blank;

}

void GamePlayScene::ccTouchCancelled( Touch *pTouch, Event *pEvent )
{
	_touchedPlace = GamePlaySceneTouchPlace::blank;

	_selectedFlag = nullptr;
	_selectedHero = nullptr;
	_selectedSoldier = nullptr;
}

void GamePlayScene::keyBackClicked()
{
	Director::getInstance()->end();
}

void GamePlayScene::keyMenuClicked()
{

}

void GamePlayScene::onEnter()
{
	Scene::onEnter();

	Director::getInstance()->getTouchDispatcher()->addTargetedDelegate(this, 0, false);
	Director::getInstance()->getKeypadDispatcher()->addDelegate(this);
}

void GamePlayScene::onExit()
{
	Scene::onExit();

	Director::getInstance()->getTouchDispatcher()->removeDelegate(this);
	Director::getInstance()->getKeypadDispatcher()->removeDelegate(this);
}


void GamePlayScene::addSceneEntities() {
	for(SceneEntityData item : LM->sceneEntities) {
		if(item.type == "img") {
			Sprite* spr = CREATE_SPRITE(item.resourceName);
			spr->setPosition(XY_SCALE(item.x + spr->getContentSize().width / 2.0f, GameManager::BASE_HEIGHT - item.y - spr->getContentSize().height));
			spr->setAnchorPoint(Point(0.5f, 0.0f));
			spr->setZOrder(static_cast<int>(GM->screenSize.height - spr->getPositionY()) + 1000);
			_gameLayer->addChild(spr);
		}
	}
}

void GamePlayScene::addTowers() {
	for(TowerInfo item : LM->towers) {
		Tower* tower = nullptr;

		switch(item.type) {
		case TowerType::castle: {
			if(!camp) {
				camp = new Camp(Point(item.x, item.y));
				tower = camp;
			}
			break;
								}
		case TowerType::rally_point: {
			Flag* flag = new Flag(Point(item.x, item.y));
			flags.push_back(flag);
			tower = flag;
			break;
									 }
		default:break;
		}

		if(tower) {
			tower->init();
			Node* sprite = tower->getSprite();
			_gameLayer->addChild(sprite);
		}
	}
}

void GamePlayScene::addMines() {
	for(TowerInfo item : LM->mines) {
		Mine* mine = new Mine(Point(item.x, item.y));
		mines.push_back(mine);
		mine->init();
		Node* sprite = mine->getSprite();
		_gameLayer->addChild(sprite);
	}
}

void GamePlayScene::updateEnemies( float ticks )
{
	for(auto it = enemies.begin(); it != enemies.end();) {
		Unit* unit = *it;
		unit->update(ticks);

		if(unit->isOver()) {
			it = enemies.erase(it);

			for(Unit* soldier : soldiers) {
				if(soldier->target == unit) {
					soldier->target = nullptr;
				}
			}

			AnimationInfo info = RM->animationInfos["bloodstain"];
			float delay = info.frames.size() * info.frameRate;

			FrameAnimation* animBloodstainFx = RM->createAnimation("bloodstain", unit->position);
			animBloodstainFx->setZOrder(0);
			animBloodstainFx->setTag(TAG_NO_ZORDER);
			animBloodstainFx->isLoop = false;
			addFrameAnimations(animBloodstainFx);

			delete unit;
		} else {
			++it;
		}
	}
}

void GamePlayScene::updateSoldiers( float ticks )
{
	for(auto it = soldiers.begin(); it != soldiers.end();) {
		Unit* unit = *it;
		unit->update(ticks);

		if(unit->isOver()) {
			it = soldiers.erase(it);

			for(Unit* enemy: enemies) {
				if(enemy->target == unit) {
					enemy->target = nullptr;
				}
			}

			if(_selectedSoldier == unit) {
				_selectedSoldier = nullptr;
			}

			delete unit;
		} else {
			++it;
		}
	}
}

void GamePlayScene::updateHeroes( float ticks )
{
	for(auto it = heroes.begin(); it != heroes.end();) {
		Unit* unit = *it;
		unit->update(ticks);

		if(unit->isOver()) {
			it = heroes.erase(it);

			for(Unit* enemy : enemies) {
				if(enemy->target == unit) {
					enemy->target = nullptr;
				}
			}

			if(_selectedHero == unit) {
				_selectedHero = nullptr;
			}

			delete unit;
		} else {
			++it;
		}
	}
}

void GamePlayScene::addEnemy( Enemy* enemy )
{
	enemies.push_back(enemy);
}

void GamePlayScene::addSoldier( Soldier* soldier)
{
	soldiers.push_back(soldier);
}

Flag* GamePlayScene::getTouchedFlag(const Point& pt){
	Flag* ret = nullptr;

	size_t n = flags.size();
	float minDistance = 999;

	for(size_t i = 0; i < n; i++) {
		Flag* flag = flags[i];
		float dist = pt.getDistance(flag->position);
		if(dist < 50.0f && dist < minDistance){
			minDistance = dist;
			ret = flag;
		}
	}

	return ret;
}

Soldier* GamePlayScene::getTouchedHero( const Point& pt )
{
	Soldier* ret = nullptr;

	float minDistance = 999;
	for(auto unit : heroes) {
		float dist = pt.getDistance(unit->position);
		if(dist < 50.0f && dist < minDistance) {
			minDistance = dist;
			ret = unit;
		}
	}

	return ret;
}

Soldier* GamePlayScene::getTouchedSoldier( const Point& pt )
{
	Soldier* ret = nullptr;

	float minDistance = 999;
	for(auto unit : soldiers) {
		float dist = pt.getDistance(unit->position);
		if(dist < 50.0f && dist < minDistance) {
			minDistance = dist;
			ret = unit;
		}
	}

	return ret;
}


void GamePlayScene::createSoldier(SoldierType type, Flag* flag) {
	Soldier* soldier = UnitFactory::createSoldier(type, this);
	soldier->setFlag(flag);
	soldier->transformState(UnitState::birth);
	addSoldier(soldier);
}

void GamePlayScene::moveSoldiers(Flag* flag)
{
	for(Unit* unit: soldiers) {
		if(!unit || !unit->isAlive()) {
			continue;
		}

		Soldier* soldier = (Soldier*)unit;
		if(soldier->myFlag == flag) {
			soldier->regroup();
		}
	}
}

void GamePlayScene::moveHero(Soldier* hero, const Point& to) {
	if(hero->target && hero->target->target == hero) {
		hero->target->target = nullptr;
	}

	hero->target = nullptr;
	hero->moveTo(to);
	hero->transformState(UnitState::walk_to_flag);
}

void GamePlayScene::updateSoldierCreationList( float ticks )
{
	for(auto it = _soldierCreationList.begin(); it != _soldierCreationList.end();) {
		SoldierCreationInfo& info = *it;
		info.delay -= ticks;

		if(info.delay < 0.0f) {
			createSoldier(info.type, info.flag);
			it = _soldierCreationList.erase(it);
		} else {
			++it;
		}
	}
}

void GamePlayScene::reorderNodes() {
	// put into array
	int count = 0;

	Array* arr = _gameLayer->getChildren();
	int n = arr->count();
	for (int i = 1; i < n; i++) {
		Node* node = (Node*)arr->objectAtIndex(i);
		if(node->getTag() == TAG_NO_ZORDER) {
			continue;
		}

		_arrSortNodes[count++] = node;
	}

	// shell sort
	n = count;
	int i, j;
	Node* temp; 
	int gap = 0;

	while (gap <= n)
	{
		gap = gap * 3 + 1;
	} 

	while (gap > 0) 
	{
		for ( i = gap; i < n; i++ )
		{
			j = i - gap;
			temp = _arrSortNodes[i];
			float y = temp->getPositionY();
			while (( j >= 0 ) && ( _arrSortNodes[j]->getPositionY() < y ))
			{
				_arrSortNodes[j + gap] = _arrSortNodes[j];
				j = j - gap;
			}
			_arrSortNodes[j + gap] = temp;
		}
		gap = ( gap - 1 ) / 3;
	}


	for(int i = 0; i < n; i++) {
		Node* node = _arrSortNodes[i];
		_gameLayer->reorderChild(node, i + 1000 ); // background items use numbers less than 100
	}
}

void GamePlayScene::addSceneNode( Node* node )
{
	_gameLayer->addChild(node);
}

void GamePlayScene::removeNodeWithFadeInOut(Node* node, float fadeInDuration, float delay, float fadeOutDuration) {
	Array arr;
	if(fadeInDuration > 0.0f) {
		arr.addObject(FadeIn::create(fadeInDuration));
	}

	if(delay > 0.0f) {
		arr.addObject(DelayTime::create(delay));
	}

	if(fadeOutDuration > 0.0f) {
		arr.addObject(FadeOut::create(fadeOutDuration));
	}

	arr.addObject(CallFuncN::create(CC_CALLBACK_1(GamePlayScene::removeNode, this)));

	node->runAction(CCSequence::create(&arr));
}

void GamePlayScene::removeNode( Node* node )
{
	if(node) {
		node->removeFromParent();
	}
}

void GamePlayScene::setSoldierRangeCircleVisibility( Flag* flag, bool visibility )
{
	for(Soldier* soldier : soldiers) {
		if(soldier->myFlag == flag) {
			soldier->rangeCircle->setVisible(visibility);
		}
	}
}

void GamePlayScene::addFrameAnimations( FrameAnimation* anim )
{
	_gameLayer->addChild(anim);
	_frameAnimations.push_back(anim);
}

void GamePlayScene::updateFrameAnimations( float ticks )
{
	for(auto it = _frameAnimations.begin(); it != _frameAnimations.end(); ) {
		FrameAnimation* anim = *it;
		anim->update(ticks);
		if(anim->finished) {
			it = _frameAnimations.erase(it);
			anim->removeFromParent();
		} else {
			++it;
		}
	}
}

